﻿using KC.Service.Core.Enums.Admin;
using KC.Framework.Base;
using KC.Framework.Extension;
using KC.Framework.Tenant;
using KC.MVC.Core.Base;
using KC.MVC.Core.Constants;
using KC.Service.Core;
using KC.Service.Core.Constants;
using KC.Service.Core.WebApiService.Business;
using KC.DTO.Account;
using KC.Service.Core.DTO.Message;

using IdentityModel;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Linq;
using System.Security;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
using System.Web;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using KC.Service.Core.Enums.Message;

namespace KC.MVC.Core.Controllers
{
    public abstract class WebBaseController : Controller
    {
        protected readonly ILogger Logger;
        protected readonly IServiceProvider ServiceProvider;

        public WebBaseController(
            IServiceProvider serviceProvider,
            ILogger logger)
        {
            Logger = logger;
            ServiceProvider = serviceProvider;
            ViewBag.CurrentAppDomain = GlobalConfig.CurrentApplication.AppDomain;
        }

        #region 文件地址
        private string _serverPath;
        protected string ServerPath
        {
            get
            {
                if (string.IsNullOrWhiteSpace(_tempDir))
                {
                    var env = (Microsoft.AspNetCore.Hosting.IHostingEnvironment)
                        HttpContext.RequestServices.GetService(
                            typeof(Microsoft.AspNetCore.Hosting.IHostingEnvironment));
                    _serverPath = env.ContentRootPath;
                }
                return _serverPath;
            }
            set { _serverPath = value; }
        }

        private string _tempDir;
        //上传使用
        protected string TempDir
        {
            get
            {
                if (string.IsNullOrWhiteSpace(_tempDir))
                {
                    _tempDir = ServerPath + "/TempDir";
                    if (!Directory.Exists(_tempDir))
                    {
                        try
                        {
                            Directory.CreateDirectory(_tempDir);
                        }
                        catch (SecurityException ex)
                        {
                            Logger.LogError(ex, ex.Message);
                        }
                    }
                }
                return _tempDir;
            }
            set { _tempDir = value; }
        }
        #endregion

        #region CurrentTenant
        protected Tenant CurrentUserTenant
        {
            get
            {
                var tenantName = CurrentUserTenantName.ToLower();
                var cacheKey = CacheKeyConstants.Prefix.TenantName + tenantName;
                var cache = Service.Core.CacheUtil.GetCache<Tenant>(cacheKey);
                if (cache != null) return cache;

                using (var scope = ServiceProvider.CreateScope())
                {
                    var tenantApiService = scope.ServiceProvider.GetRequiredService<ITenantUserApiService>();
                    cache = tenantApiService.GetTenantByName(tenantName).Result;
                    if (cache == null) return null;

                    Service.Core.CacheUtil.SetCache(cacheKey, cache, TimeSpan.FromMinutes(TimeOutConstants.CacheTimeOut));

                    return cache;
                }
            }
        }

        /// <summary>
        /// 登录用户所属企业的TenantName
        /// </summary>
        protected string CurrentUserTenantName
        {
            get
            {
                if (Identity != null && Identity.FindFirst(OpenIdConnectConstants.ClaimTypes_TenantName) != null)
                {
                    return Identity.FindFirst(OpenIdConnectConstants.ClaimTypes_TenantName).Value;
                }

                return string.Empty;
            }
        }
        /// <summary>
        /// 当前登录用户的企业别名
        /// </summary>
        protected string CurrentUserTenantNickName
        {
            get
            {
                try
                {
                    if (Identity == null)
                        return string.Empty;

                    return string.IsNullOrWhiteSpace(CurrentUserTenant?.NickName)
                        ? CurrentUserTenant?.TenantName
                        : CurrentUserTenant?.NickName;
                }
                catch (Exception ex)
                {
                    Logger.LogError(ex, ex.Message);
                    return string.Empty;
                }
            }
        }
        /// <summary>
        /// 登录企业所属企业名称
        /// </summary>
        protected string CurrentUserTenantDisplayName
        {
            get
            {
                try
                {
                    return CurrentUserTenant?.TenantDisplayName;
                }
                catch (Exception ex)
                {
                    Logger.LogError(ex, ex.Message);
                    return string.Empty;
                }
            }
        }
        #endregion

        #region CurrentUser
        private ClaimsIdentity _identity;
        protected ClaimsIdentity Identity
        {
            get
            {
                if (_identity != null)
                    return _identity;

                if (User != null && User.Identity.IsAuthenticated)
                {
                    _identity = User.Identity as ClaimsIdentity;
                }

                return _identity;
            }
        }

        protected CurrentUser CurrentUser
        {
            get
            {
                return GetCachedCurrentUser()?.Result;
            }
        }

        protected void RemoveCurrentUserCache()
        {
            var lowerTenant = CurrentUserTenantName?.ToLower();
            var userCacheKey = lowerTenant + "-" + CacheKeyConstants.Prefix.CurrentUserId + CurrentUserId;
            var menuCacheKey = lowerTenant + "-CurrentUserMenus-" + CurrentUserId;
            var permissionCacheKey = lowerTenant + "-CurrentUserPermissions-" + CurrentUserId;

            CacheUtil.RemoveCache(userCacheKey);
            CacheUtil.RemoveCache(menuCacheKey);
            CacheUtil.RemoveCache(permissionCacheKey);
        }

        /// <summary>
        /// 获取当前用户的基本信息及组织结构信息
        /// </summary>
        /// <param name="container"></param>
        /// <param name="tenant"></param>
        /// <returns></returns>
        protected async Task<CurrentUser> GetCachedCurrentUser()
        {
            if (!this.User.Identity.IsAuthenticated)
            {
                Response.StatusCode = (int)System.Net.HttpStatusCode.Unauthorized;
                throw new UnauthorizedAccessException("用户未登录");//必须要用户验证后才能使用CurrentUser
            }
            var userId = CurrentUserId;
            if (string.IsNullOrEmpty(userId))
            {
                Response.StatusCode = (int)System.Net.HttpStatusCode.Unauthorized;
                throw new UnauthorizedAccessException("用户Id为空");//必须要用户验证后才能使用CurrentUser
            }

            var lowerTenant = CurrentUserTenantName?.ToLower();
            var cacheKey = lowerTenant + "-" + CacheKeyConstants.Prefix.CurrentUserId + userId;

            //Logger.LogDebug(string.Format("---Tenant：[{0}] User:[{1}] get Claims: " , lowerTenant, userId, User.Claims.ToList().ToCommaSeparatedStringByFilter(m => m.Type)));

            var cache = await Service.Core.CacheUtil.GetCacheAsync<CurrentUser>(cacheKey);
            if (cache == null)
            {
                var currentUser = new CurrentUser();
                currentUser.UserId = userId;
                currentUser.UserName = CurrentUserName;
                currentUser.UserPhone = CurrentUserPhone;
                currentUser.UserEmail = CurrentUserEmail;
                currentUser.UserDisplayName = CurrentUserDisplayName;
                currentUser.RoleIds = CurrentUserRoleIds;

                //Logger.LogDebug("--" + CurrentUserName + "--" + CurrentUserPhone + "--" + CurrentUserEmail + "--" + CurrentUserDisplayName + "--" + CurrentUserRoleIds.ToCommaSeparatedString());

                currentUser.CurrentTenantName = CurrentUserTenant?.TenantName;
                currentUser.CurrentTenantNickName = CurrentUserTenant?.NickName;
                currentUser.CurrentTenantDisplayName = CurrentUserTenant?.TenantDisplayName;
                currentUser.CurrentTenantTenantType = CurrentUserTenant != null ? CurrentUserTenant.TenantType : TenantType.StoreCredit;

                using (var scope = ServiceProvider.CreateScope())
                {
                    var accountService = scope.ServiceProvider.GetRequiredService<IAccountApiService>();

                    //后台用户需要组织架构
                    var user = await accountService.GetSimpleUserWithOrganizationsByUserId(userId);
                    if (user == null)
                        return currentUser;

                    currentUser.UserName = user.UserName;
                    currentUser.UserPhone = user.PhoneNumber;
                    currentUser.UserEmail = user.Email;
                    currentUser.UserDisplayName = user.DisplayName;
                    currentUser.UserCode = user.ReferenceId1;
                    currentUser.UserPositionLevel = user.PositionLevel;
                    currentUser.OrganizationIds = user.UserOrgIds;
                    currentUser.UserPositionLevel = user.PositionLevel;
                    currentUser.RoleIds = user.UserRoleIds;

                    await Service.Core.CacheUtil.SetCacheAsync(cacheKey, currentUser, TimeSpan.FromMinutes(TimeOutConstants.CacheTimeOut));
                }

                return currentUser;
            }
            else
            {
                return cache;
            }
        }
        /// <summary>
        /// 获取当前登录用户的菜单数据
        /// </summary>
        /// <param name="container"></param>
        /// <param name="tenant"></param>
        /// <returns></returns>
        protected async Task<List<MenuNodeSimpleDTO>> GetCachedCurrentUserMenus()
        {
            var currentUser = await GetCachedCurrentUser();
            if (currentUser == null)
            {
                throw new UnauthorizedAccessException("用户未登录");//必须要用户验证后才能使用CurrentUser
            }

            var lowerTenant = CurrentUserTenantName?.ToLower();
            var cacheKey = lowerTenant + "-CurrentUserMenus-" + currentUser.UserId;
            var cache = await Service.Core.CacheUtil.GetCacheAsync<List<MenuNodeSimpleDTO>>(cacheKey);
            //Logger.LogDebug(string.Format("-----Get CurrentUser cache is null? {0}", cache == null ? "True" : "Flase"));
            if (cache == null || !cache.Any())
            {
                var currentUserMenus = new List<MenuNodeSimpleDTO>();
                using (var scope = ServiceProvider.CreateScope())
                {
                    var accountService = scope.ServiceProvider.GetRequiredService<IAccountApiService>();

                    #region 后台用户需要权限、角色、组织、菜单等信息

                    var menus = await accountService.GetUserMenusByRoleIds(currentUser.RoleIds);
                    if (menus == null)
                        return new List<MenuNodeSimpleDTO>();

                    //店铺赊销及供应链分期，使用店铺赊销的菜单，条件：(m.TenantType & TenantType.StoreCredit) != 0
                    //var tallys = TenantType.StoreCredit | TenantType.SupplyChainFinance;
                    //if ((CurrentUserTenant.TenantType & tallys) != 0) //enum对象的位移操作
                    //{
                    //    currentUserMenus =
                    //        menus.Where(m => m.TenantType == null || m.ApplicationId != TenantConstant.MarketAppId ||
                    //                         (m.ApplicationId == TenantConstant.MarketAppId && m.TenantType != null &&
                    //                          (m.TenantType & tallys) != 0))
                    //            .ToList();
                    //}
                    //else
                    //{
                    //    currentUserMenus =
                    //        menus.Where(m => m.TenantType == null || m.ApplicationId != TenantConstant.MarketAppId ||
                    //                         (m.ApplicationId == TenantConstant.MarketAppId && m.TenantType != null &&
                    //                          (m.TenantType & tallys) == 0))
                    //            .ToList();
                    //}
                    #endregion

                    currentUserMenus = menus;
                    await Service.Core.CacheUtil.SetCacheAsync(cacheKey, currentUserMenus, TimeSpan.FromMinutes(TimeOutConstants.CacheTimeOut));
                }
                return currentUserMenus;
            }
            else
            {
                return cache;
            }
        }
        /// <summary>
        /// 获取当前登录用户的权限数据
        /// </summary>
        /// <param name="container"></param>
        /// <param name="tenant"></param>
        /// <returns></returns>
        protected async Task<List<PermissionSimpleDTO>> GetCachedCurrentUserPermissions()
        {
            var currentUser = await GetCachedCurrentUser();
            if (currentUser == null)
            {
                throw new UnauthorizedAccessException("用户未登录");//必须要用户验证后才能使用CurrentUser
            }

            var lowerTenant = CurrentUserTenantName?.ToLower();
            var cacheKey = lowerTenant + "-CurrentUserPermissions-" + currentUser.UserId;
            var cache = await Service.Core.CacheUtil.GetCacheAsync<List<PermissionSimpleDTO>>(cacheKey);
            //Logger.LogInformation(string.Format("-----Get CurrentUser cache is null? {0}", cache == null ? "True" : "Flase"));
            if (cache == null)
            {
                var currentUserPermissions = new List<PermissionSimpleDTO>();
                using (var scope = ServiceProvider.CreateScope())
                {
                    var accountService = scope.ServiceProvider.GetRequiredService<IAccountApiService>();

                    #region 后台用户需要权限、角色、组织、菜单等信息

                    var permissions = await accountService.GetUserPermissionsByRoleIds(currentUser.RoleIds);
                    if (permissions == null)
                        return new List<PermissionSimpleDTO>();

                    //店铺赊销及供应链分期，使用店铺赊销的菜单，条件：(m.TenantType & TenantType.StoreCredit) != 0
                    //var tallys = TenantType.StoreCredit | TenantType.SupplyChainFinance;
                    //var iTallys = (int)tallys;
                    //if ((CurrentUserTenant.TenantType & tallys) != 0) //enum对象的位移操作
                    //{
                    //    currentUserPermissions =
                    //        permissions.Where(m => m.TenantType == null || m.ApplicationId != TenantConstant.MarketAppId ||
                    //                         (m.ApplicationId == TenantConstant.MarketAppId && m.TenantType != null &&
                    //                          (m.TenantType & tallys) != 0))
                    //            .ToList();
                    //}
                    //else
                    //{
                    //    currentUserPermissions =
                    //        permissions.Where(m => m.TenantType == null || m.ApplicationId != TenantConstant.MarketAppId ||
                    //                         (m.ApplicationId == TenantConstant.MarketAppId && m.TenantType != null &&
                    //                          (m.TenantType & tallys) == 0))
                    //            .ToList();
                    //}

                    #endregion

                    currentUserPermissions = permissions;
                    await Service.Core.CacheUtil.SetCacheAsync(cacheKey, currentUserPermissions, TimeSpan.FromMinutes(TimeOutConstants.CacheTimeOut));
                }
                return currentUserPermissions;
            }
            else
            {
                return cache;
            }
        }

        protected void RemoveCachedCurrentUser(string tenant, string userId)
        {
            if (string.IsNullOrEmpty(userId))
            {
                throw new UnauthorizedAccessException("用户Id为空");//必须要用户验证后才能使用CurrentUser
            }

            var lowerTenant = tenant.ToLower();
            var cacheKey = lowerTenant + "-" + CacheKeyConstants.Prefix.CurrentUserId + userId;
            Service.Core.CacheUtil.RemoveCache(cacheKey);

            cacheKey = lowerTenant + "-CurrentUserMenus-" + userId;
            Service.Core.CacheUtil.RemoveCache(cacheKey);

            cacheKey = lowerTenant + "-CurrentUserPermissions-" + userId;
            Service.Core.CacheUtil.RemoveCache(cacheKey);
        }

        /// <summary>
        /// 登录用户Id
        /// </summary>
        protected string CurrentUserId
        {
            get
            {
                var userId = string.Empty;
                if (this.User != null
                    && this.User.Identity.IsAuthenticated)
                {
                    if (this.User.FindFirst(ClaimTypes.NameIdentifier) != null)
                    {
                        userId = this.User.FindFirst(ClaimTypes.NameIdentifier).Value;
                    }
                    else if (this.User.FindFirst(IdentityModel.JwtClaimTypes.Subject) != null)
                    {
                        userId = this.User.FindFirst(IdentityModel.JwtClaimTypes.Subject).Value;
                    }
                    else if (Identity != null
                        && Identity.FindFirst(ClaimTypes.NameIdentifier) != null)
                    {
                        userId = Identity.FindFirst(ClaimTypes.NameIdentifier).Value;
                    }
                    else if (Identity != null
                        && Identity.FindFirst(IdentityModel.JwtClaimTypes.Subject) != null)
                    {
                        userId = Identity.FindFirst(IdentityModel.JwtClaimTypes.Subject).Value;
                    }
                }
                return userId;
            }
        }
        /// <summary>
        /// 登录用户名
        /// </summary>
        protected string CurrentUserName
        {
            get
            {
                if (Identity != null && Identity.FindFirst(ClaimTypes.Name) != null)
                {
                    return Identity.FindFirst(ClaimTypes.Name).Value;
                }
                else if (Identity != null && Identity.FindFirst(JwtClaimTypes.Name) != null)
                {
                    return Identity.FindFirst(JwtClaimTypes.Name).Value;
                }

                return string.Empty;
            }
        }
        /// <summary>
        /// 用户显示名
        /// </summary>
        protected string CurrentUserDisplayName
        {
            get
            {
                if (Identity != null && Identity.FindFirst(ClaimTypes.GivenName) != null)
                {
                    return Identity.FindFirst(ClaimTypes.GivenName).Value;
                }
                else if (Identity != null && Identity.FindFirst(JwtClaimTypes.GivenName) != null)
                {
                    return Identity.FindFirst(JwtClaimTypes.GivenName).Value;
                }
                else if (Identity != null && Identity.FindFirst(OpenIdConnectConstants.ClaimTypes_DisplayName) != null)
                {
                    return Identity.FindFirst(OpenIdConnectConstants.ClaimTypes_DisplayName).Value;
                }

                return string.Empty;
            }
        }
        /// <summary>
        /// 登录用户邮箱
        /// </summary>
        protected string CurrentUserEmail
        {
            get
            {
                if (Identity != null && Identity.FindFirst(ClaimTypes.Email) != null)
                {
                    return Identity.FindFirst(ClaimTypes.Email).Value;
                }
                else if (Identity != null && Identity.FindFirst(JwtClaimTypes.Email) != null)
                {
                    return Identity.FindFirst(JwtClaimTypes.Email).Value;
                }
                else if (Identity != null && Identity.FindFirst(OpenIdConnectConstants.ClaimTypes_Email) != null)
                {
                    return Identity.FindFirst(OpenIdConnectConstants.ClaimTypes_Email).Value;
                }

                return string.Empty;
            }
        }
        /// <summary>
        /// 登录用户手机
        /// </summary>
        protected string CurrentUserPhone
        {
            get
            {
                if (Identity != null && Identity.FindFirst(ClaimTypes.MobilePhone) != null)
                {
                    return Identity.FindFirst(ClaimTypes.MobilePhone).Value;
                }
                else if (Identity != null && Identity.FindFirst(JwtClaimTypes.PhoneNumber) != null)
                {
                    return Identity.FindFirst(JwtClaimTypes.PhoneNumber).Value;
                }
                else if (Identity != null && Identity.FindFirst(OpenIdConnectConstants.ClaimTypes_Phone) != null)
                {
                    return Identity.FindFirst(OpenIdConnectConstants.ClaimTypes_Phone).Value;
                }
                return string.Empty;
            }
        }
        /// <summary>
        /// 登录用户角色Id列表
        /// </summary>
        protected List<string> CurrentUserRoleIds
        {
            get
            {
                if (Identity != null && Identity.FindAll(ClaimTypes.Role).Any())
                {
                    return Identity.FindAll(ClaimTypes.Role).Select(r => r.Value).ToList();
                }
                else if (Identity != null && Identity.FindAll(JwtClaimTypes.Role).Any())
                {
                    return Identity.FindAll(JwtClaimTypes.Role).Select(r => r.Value).ToList();
                }
                else if (Identity != null && Identity.FindAll(OpenIdConnectConstants.ClaimTypes_RoleId).Any())
                {
                    return Identity.FindAll(OpenIdConnectConstants.ClaimTypes_RoleId).Select(r => r.Value).ToList();
                }

                return new List<string>();
            }
        }

        /// <summary>
        /// 登录用户角色Id列表
        /// </summary>
        protected List<int> CurrentUserOrgIds
        {
            get
            {
                if (Identity != null && Identity.FindAll(OpenIdConnectConstants.ClaimTypes_RoleId).Any())
                {
                    return Identity.FindAll(OpenIdConnectConstants.ClaimTypes_OrgId).Select(r => int.Parse(r.Value)).ToList();
                }

                return new List<int>();
            }
        }

        #region 弃用
        protected string CurrentUserType
        {
            get
            {
                if (Identity != null && Identity.FindFirst(ClaimTypes.Gender) != null)
                {
                    return Identity.FindFirst(ClaimTypes.Gender).Value;
                }
                else if (Identity != null && Identity.FindFirst(JwtClaimTypes.Gender) != null)
                {
                    return Identity.FindFirst(JwtClaimTypes.Gender).Value;
                }
                return string.Empty;
            }
        }

        protected string CurrentUserMemberId
        {
            get
            {
                if (Identity != null && Identity.FindFirst(ClaimTypes.PrimarySid) != null)
                {
                    return Identity.FindFirst(ClaimTypes.PrimarySid).Value;
                }

                return string.Empty;
            }
        }
        protected string CurrentUserTelephone
        {
            get
            {
                if (Identity != null && Identity.FindFirst(ClaimTypes.HomePhone) != null)
                {
                    return Identity.FindFirst(ClaimTypes.HomePhone).Value;
                }
                return string.Empty;
            }
        }
        protected string CurrentUserLoginCount
        {
            get
            {
                if (Identity != null && Identity.FindFirst(ClaimTypes.Locality) != null)
                {
                    return Identity.FindFirst(ClaimTypes.Locality).Value;
                }

                return string.Empty;
            }
        }
        #endregion

        protected string CurrentUserCode
        {
            get
            {
                if (Identity != null && Identity.FindFirst(ClaimTypes.Surname) != null)
                {
                    return Identity.FindFirst(ClaimTypes.Surname).Value;
                }
                else
                {
                    return CurrentUser.UserCode;
                }
            }
        }
        protected PositionLevel CurrentUserPosition
        {
            get
            {
                return CurrentUser.UserPositionLevel;
            }
        }

        #region 应用相关Id及地址
        private Guid _appId;
        protected Guid CurrentOperationApplicationId
        {
            get
            {
                if (_appId != Guid.Empty)
                    return _appId;
                return GlobalConfig.ApplicationGuid;
            }

            set
            {
                _appId = value;
            }
        }

        protected string CurrentUserMarketDomain
        {
            get
            {
                if (!string.IsNullOrEmpty(CurrentUserTenantName))
                {
                    return GlobalConfig.JRWebDomain.Replace(TenantConstant.SubDomain, CurrentUserTenantName);
                }

                return null;
            }
        }
        protected string CurrentUserShopDomain
        {
            get
            {
                if (!string.IsNullOrEmpty(CurrentUserTenantName))
                {
                    return GlobalConfig.PortalWebDomain.Replace(TenantConstant.SubDomain, CurrentUserTenantName);
                }

                return null;
            }
        }
        private string _currentUserDomain;
        protected string CurrentUserDomain
        {
            get
            {
                if (!string.IsNullOrWhiteSpace(this._currentUserDomain))
                    return this._currentUserDomain;
                return null;
            }

            set
            {
                this._currentUserDomain = value;
                this.ViewData[CustomConstants.CurrentUserDomain] = value;
            }
        }
        #endregion

        /// <summary>
        /// 是否为公司系统管理员
        /// </summary>
        protected bool IsSystemAdmin
        {
            get
            {
                //管理员角色
                return CurrentUserRoleIds.Contains(RoleConstants.AdminRoleId);
            }
        }
        /// <summary>
        /// 是否为公司高管
        /// </summary>
        protected bool IsCEO
        {
            get
            {
                //如果是顶级组织机构的管理员，则获取全公司用户的数据
                if ((CurrentUserPosition == PositionLevel.DepartmentMananger
                    || CurrentUserPosition == PositionLevel.DepartmentSubMananger))
                    return true;

                return false;
            }
        }

        protected bool IsClubUser
        {
            get
            {
                return Identity != null && Identity.FindFirst(OpenIdConnectConstants.ClaimTypes_TenantName) != null;
            }
        }
        #endregion

        #region Get JsonResult
        /// <summary>
        /// 返回boolean类型的快捷方式
        /// </summary>
        /// <param name="success">是否成功</param>
        /// <param name="message">失败后的消息</param>
        /// <returns></returns>
        protected JsonResult ThrowErrorJsonMessage(bool success, string message)
        {
            return Json(new { success = success, message = message });
        }

        /// <summary>
        /// 获取ServiceResult<T>对象的JsonResult
        /// </summary>
        /// <typeparam name="T">所需返回的对象</typeparam>
        /// <param name="func">Lamdba表达式</param>
        /// <returns>ServiceResult<T></returns>
        protected JsonResult GetServiceJsonResult<T>(Func<T> func)
        {
            var result = ServiceWrapper.Invoke(
                "ControllerBase",
                func.Method.Name,
                func,
                Logger);
            return Json(result);
        }

        /// <summary>
        /// 获取ServiceResult<T>对象的JsonResult（异步方法）
        /// </summary>
        /// <typeparam name="T">所需返回的对象</typeparam>
        /// <param name="func">Lamdba表达式</param>
        /// <returns>ServiceResult<T></returns>
        protected async Task<JsonResult> GetServiceJsonResultAsync<T>(Func<Task<T>> func)
        {
            var result = await ServiceWrapper.InvokeAsync<T>(
                "ControllerBase",
                func.Method.Name,
                func,
                Logger);
            return Json(result);
        }
        /// <summary>
        /// 获取ServiceResult<T>对象
        /// </summary>
        /// <typeparam name="T">所需返回的对象</typeparam>
        /// <param name="func">Lamdba表达式</param>
        /// <returns>ServiceResult<T></returns>
        protected ServiceResult<T> GetServiceResult<T>(Func<T> func)
        {
            return ServiceWrapper.Invoke(
                "ControllerBase",
                func.Method.Name,
                func,
                Logger);
        }
        /// <summary>
        /// 获取ServiceResult<T>对象（异步方法）
        /// </summary>
        /// <typeparam name="T">所需返回的对象</typeparam>
        /// <param name="func">Lamdba表达式</param>
        /// <returns>ServiceResult<T></returns>
        protected async Task<ServiceResult<T>> GetServiceResultAsync<T>(Func<Task<T>> func)
        {
            var result = await ServiceWrapper.InvokeAsync<T>(
                "ControllerBase",
                func.Method.Name,
                func,
                Logger);
            return result;
        }

        #endregion

        #region SelectItem from Enums
        /// <summary>
        /// 根据Enum对象，获取DropDownItems所需的数据
        /// </summary>
        /// <typeparam name="T">Enum类型</typeparam>
        /// <param name="exceptEnums">需要排除的Enum类型对象</param>
        /// <param name="selectedEnumValues">选中的Enum对象值</param>
        /// <returns></returns>
        protected IEnumerable<SelectListItem> GetDropDownItemsByEnum<T>(List<T> exceptEnums = null, List<int> selectedEnumValues = null)
        {
            if (exceptEnums != null)
            {
                var enumList = EnumExtensions.GetEnumDictionary(exceptEnums);
                return
                    enumList.Select(
                        item => new SelectListItem
                        {
                            Text = item.Value,
                            Value = item.Key.ToString(),
                            Selected = selectedEnumValues != null && selectedEnumValues.Contains(item.Key),
                        });
            }
            else
            {
                var enumList = EnumExtensions.GetEnumDictionary<T>();
                return
                    enumList.Select(
                        item => new SelectListItem
                        {
                            Text = item.Value,
                            Value = item.Key.ToString(),
                            Selected = selectedEnumValues != null && selectedEnumValues.Contains(item.Key),
                        });
            }
        }
        /// <summary>
        /// 根据Enum对象，获取DropDownItems所需的数据（添加自定义选项，例如：请选择）
        /// </summary>
        /// <typeparam name="T">Enum类型</typeparam>
        /// <param name="exceptEnums">需要排除的Enum类型对象</param>
        /// <param name="allText">自定义首选项，值为空，默认为：请选择</param>
        /// <returns></returns>
        protected IEnumerable<SelectListItem> GetDropDownItemsByEnumWithAll<T>(List<T> exceptEnums = null, string allText = null)
        {
            allText = allText ?? "请选择";
            var enumList = exceptEnums != null
                    ? EnumExtensions.GetEnumDictionary<T>(exceptEnums)
                    : EnumExtensions.GetEnumDictionary<T>();
            var result = new List<SelectListItem> { new SelectListItem() { Text = allText, Value = "" } };
            foreach (var item in enumList)
            {
                result.Add(new SelectListItem { Text = item.Value, Value = item.Key.ToString() });
            }
            return result;
        }
        /// <summary>
        /// 根据Enum对象，获取DropDownItems所需的数据
        /// </summary>
        /// <typeparam name="T">Enum类型</typeparam>
        /// <param name="exceptEnums">需要排除的Enum类型对象</param>
        /// <param name="selectedEnumValues">选中的Enum对象列表</param>
        /// <returns></returns>
        protected IEnumerable<SelectListItem> GetDropDownItemsByEnumWithFilter<T>(List<T> exceptEnums = null, List<int> selectedEnumValues = null)
        {
            var enumList = exceptEnums != null
                    ? EnumExtensions.GetEnumDictionary<T>(exceptEnums)
                    : EnumExtensions.GetEnumDictionary<T>();
            var result = new List<SelectListItem> { new SelectListItem() { Text = "请选择", Value = "" } };
            foreach (var item in enumList)
            {
                result.Add(new SelectListItem
                {
                    Text = item.Value,
                    Value = item.Key.ToString(),
                    Selected = selectedEnumValues != null && selectedEnumValues.Contains(item.Key)
                });
            }
            return result;
        }
        /// <summary>
        /// 根据Enum对象，获取DropDownItems所需的数据
        /// </summary>
        /// <typeparam name="T">Enum类型</typeparam>
        /// <param name="value">选中的Enum对象值</param>
        /// <param name="exceptEnums">需要排除的Enum类型对象</param>
        /// <returns></returns>
        protected IEnumerable<SelectListItem> GetDropDownItemsByEnumWithAll<T>(int? value, List<T> exceptEnums = null)
        {
            var enumList = exceptEnums != null
                    ? EnumExtensions.GetEnumDictionary<T>(exceptEnums)
                    : EnumExtensions.GetEnumDictionary<T>();

            var result = new List<SelectListItem> { new SelectListItem() { Text = "请选择", Value = "" } };
            foreach (var item in enumList)
            {
                result.Add(new SelectListItem
                {
                    Text = item.Value,
                    Value = item.Key.ToString(),
                    Selected = item.Key.Equals(value)
                });
            }
            return result;
        }

        #endregion SelectItem from Enums

        #region 树结构操作方法

        /// <summary>
        /// 根据树结构的列表，返回一颗树结构对象（以parent对象返回）
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="parent">根节点：ParentId==null</param>
        /// <param name="allOrgs">树结构列表（非树结构）</param>
        /// <param name="predict">相关条件</param>
        protected void GetSimpleTreeWithChild<T>(T parent, List<T> allOrgs, Func<T, bool> predict) where T : DTO.Core.TreeNodeSimpleDTO<T>
        {
            if (predict == null)
            {
                var child = allOrgs.Where(m => m.ParentId.Equals(parent.id)).OrderBy(m => m.Index).ToList();
                parent.children = child.ToList();
                foreach (var children in child)
                {
                    GetSimpleTreeWithChild(children, allOrgs, predict);
                }
            }
            else
            {
                var child = allOrgs.Where(m => m.ParentId.Equals(parent.id)).Where(predict).OrderBy(m => m.Index).ToList();
                parent.children = child.ToList();
                foreach (var children in child)
                {
                    GetSimpleTreeWithChild(children, allOrgs, predict);
                }
            }
        }
        /// <summary>
        /// 根据树结构的列表，返回一颗树结构对象（以根节点：parent对象返回）
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="parent">根节点：ParentId==null</param>
        /// <param name="allOrgs">树结构列表（非树结构）</param>
        /// <param name="predict">相关条件</param>
        protected void GetTreeWithChild<T>(T parent, List<T> allOrgs, Func<T, bool> predict) where T : DTO.Core.TreeNodeDTO<T>
        {
            if (predict == null)
            {
                var child = allOrgs.Where(m => m.ParentId.Equals(parent.id)).OrderBy(m => m.Index).ToList();
                parent.children = child.ToList();
                foreach (var children in child)
                {
                    GetTreeWithChild(children, allOrgs, predict);
                }
            }
            else
            {
                var child = allOrgs.Where(m => m.ParentId.Equals(parent.id)).Where(predict).OrderBy(m => m.Index).ToList();
                parent.children = child.ToList();
                foreach (var children in child)
                {
                    GetTreeWithChild(children, allOrgs, predict);
                }
            }

        }

        /// <summary>
        /// 根据树结构对象，返回树结构的列表（以result对象返回）
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="result"></param>
        /// <param name="parent"></param>
        /// <param name="predict"></param>
        protected void GetSimpleListByTreeNodeWithChild<T>(List<T> result, T parent, Func<T, bool> predict) where T : DTO.Core.TreeNodeSimpleDTO<T>
        {
            if (predict == null)
            {
                var child = parent.children.OrderBy(m => m.Index).ToList();
                if (child != null && child.Any())
                {
                    foreach (var children in child)
                    {
                        GetSimpleListByTreeNodeWithChild(result, children, predict);
                    }
                }
                parent.children = null;
                result.Add(parent);
            }
            else
            {
                var child = parent.children.Where(predict).OrderBy(m => m.Index).ToList();
                if (child != null && child.Any())
                {
                    foreach (var children in child)
                    {
                        GetSimpleListByTreeNodeWithChild(result, children, predict);
                    }
                }
                parent.children = null;
                result.Add(parent);
            }
        }

        /// <summary>
        /// 根据树结构对象，返回树结构的列表（以result对象返回）
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="result"></param>
        /// <param name="parent"></param>
        /// <param name="predict"></param>
        protected void GetListByTreeNodeWithChild<T>(List<T> result, T parent, Func<T, bool> predict) where T : DTO.Core.TreeNodeDTO<T>
        {
            if (predict == null)
            {
                var child = parent.children.OrderBy(m => m.Index).ToList();
                if (child != null && child.Any())
                {
                    foreach (var children in child)
                    {
                        GetListByTreeNodeWithChild(result, children, predict);
                    }
                }
                parent.children = null;
                result.Add(parent);
            }
            else
            {
                var child = parent.children.Where(predict).OrderBy(m => m.Index).ToList();
                if (child != null && child.Any())
                {
                    foreach (var children in child)
                    {
                        GetListByTreeNodeWithChild(result, children, predict);
                    }
                }
                parent.children = null;
                result.Add(parent);
            }
        }
        #endregion

        #region 获取菜单/权限数据
        /// <summary>
        /// 主页面载入菜单
        /// </summary>
        /// <returns></returns>
        public async Task<IActionResult> LoadMenus()
        {
            if (!User.Identity.IsAuthenticated)
                return new ChallengeResult();

            Func<MenuNodeSimpleDTO, bool> predicate = m => true;
            var menuTrees = await GetCurrentUserMenuTree(predicate);
            return Json(AutoMapper.Mapper.Map<List<MenuNodeFrontendDTO>>(menuTrees));
        }
        /// <summary>
        /// 根据菜单的Url，返回菜单Id
        /// </summary>
        /// <param name="url"></param>
        /// <returns></returns>
        public async Task<IActionResult> GetMenuIdByUrl(string url)
        {
            if (!User.Identity.IsAuthenticated)
                return new ChallengeResult();

            return await GetServiceJsonResultAsync(async () =>
            {
                var allmenus = await GetCachedCurrentUserMenus();

                var menu = allmenus.FirstOrDefault(m => m.URL.Contains(url));
                return menu != null ? menu.id : 0;
            });
        }
        /// <summary>
        /// 获取当前用户的菜单树结构
        /// </summary>
        /// <param name="predict"></param>
        /// <returns></returns>
        protected async Task<List<MenuNodeSimpleDTO>> GetCurrentUserMenuTree(Func<MenuNodeSimpleDTO, bool> predict)
        {
            var result = new List<MenuNodeSimpleDTO>();
            var allmenus = await GetCachedCurrentUserMenus();
            var menus = allmenus.Where(predict).OrderBy(m => m.Index).ToList();
            foreach (var parent in menus.Where(m => m.ParentId == null))
            {
                SetUrlWithDomain(parent, menus);
                result.Add(parent);
            }
            return result;
        }
        /// <summary>
        /// 获取当前用户的权限树结构
        /// </summary>
        /// <param name="name">权限名称</param>
        /// <returns></returns>
        protected async Task<List<PermissionSimpleDTO>> GetCurrentPermissionTree(string name = "")
        {
            Func<PermissionSimpleDTO, bool> predict = m => true;
            if (!string.IsNullOrWhiteSpace(name))
            {
                predict = m => (m.text.Contains(name));
            }

            var result = new List<PermissionSimpleDTO>();
            var userPermissions = await GetCachedCurrentUserPermissions();
            var menus = userPermissions.Where(predict).ToList();
            //查询将父级菜单也带出来
            if (!string.IsNullOrWhiteSpace(name))
            {
                var menusParentId = menus.Where(c => c.ParentId != null).Select(c => c.ParentId).Distinct().ToList();
                for (int i = 0; i < menusParentId.Count(); i++)
                {
                    if (menus.Count(c => c.id == menusParentId[i]) == 0)
                    {
                        menus.Add(userPermissions.FirstOrDefault(c => c.id == menusParentId[i]));
                    }
                }
            }
            foreach (var parent in menus.Where(m => m.ParentId == null))
            {
                GetActionPermissionWithChild(parent, menus);
                result.Add(parent);
            }
            return result;
        }

        private void GetActionPermissionWithChild(PermissionSimpleDTO parent, List<PermissionSimpleDTO> allOrgs)
        {
            var child = allOrgs.Where(m => m.ParentId.Equals(parent.id)).ToList();
            parent.children = child.ToList();
            foreach (var children in child)
            {
                children.ParentName = parent.text;
                GetActionPermissionWithChild(children, allOrgs);
            }
        }
        /// <summary>
        /// 拼装菜单对象的URL，以full Url的方式，例如：http://tenantName.sso.domain.com/area/controller/action?query=
        /// </summary>
        /// <param name="parent"></param>
        /// <param name="allOrgs"></param>
        private void SetUrlWithDomain(MenuNodeSimpleDTO parent, List<MenuNodeSimpleDTO> allOrgs)
        {
            var child = allOrgs.Where(m => m.ParentId.Equals(parent.id)).OrderBy(m => m.Index).ToList();
            parent.children = child.ToList();
            var app = GlobalConfig.Applications.FirstOrDefault(m => m.AppId == parent.ApplicationId);
            if (app != null && !string.IsNullOrEmpty(app.AppDomain))
            {
                var domain = app.AppDomain.Replace(TenantConstant.SubDomain, CurrentUserTenantName);
                if (app.AppId == ApplicationConstant.AdminAppId)
                    domain = app.AppDomain.Replace("localhost", CurrentUserTenantName + ".localhost");
                parent.URL = domain.EndsWith("/") ? domain.ReplaceLast("/", "") + parent.URL : domain + parent.URL;
            }

            foreach (var children in child)
            {
                SetUrlWithDomain(children, allOrgs);
            }
        }

        #endregion

        #region 获取消息数据
        /// <summary>
        /// 主页获取通知消息
        /// </summary>
        /// <returns></returns>
        public JsonResult LoadMessages()
        {
            var messages = new List<MemberMessageStatusDTO>();
            messages.Add(new MemberMessageStatusDTO()
            {
                Id = 1,
                Status = MessageStatus.Unread,
                Title = "Validators",
                CreatedDate = new DateTime(2018, 12, 19, 8, 16, 12),
                MemberRemindMessage = "In order to provide the best possible experience to old and buggy browsers, Font Awesome uses CSS browser hacks in several places to target special CSS to certain browser versions in order to work around bugs in the browsers themselves. These hacks understandably cause CSS validators to complain that they are invalid. In a couple places, we also use bleeding-edge CSS features that aren't yet fully standardized, but these are used purely for progressive enhancement.<br/>" +
                "These validation warnings don't matter in practice since the non-hacky portion of our CSS does fully validate and the hacky portions don't interfere with the proper functioning of the non - hacky portion,hence why we deliberately ignore these particular warnings.",
            });
            messages.Add(new MemberMessageStatusDTO()
            {
                Id = 1,
                Status = MessageStatus.Unread,
                Title = "Internet Explorer 8 and @font-face",
                CreatedDate = new DateTime(2019, 1, 1, 18, 21, 32),
                MemberRemindMessage = "IE8 has some issues with @font-face when combined with :before. Font Awesome uses that combination. If a page is cached, and loaded without the mouse over the window (i.e. hit the refresh button or load something in an iframe) then the page gets rendered before the font loads. Hovering over the page (body) will show some of the icons and hovering over the remaining icons will show those as well. See issue #954 for details.",
            });
            messages.Add(new MemberMessageStatusDTO()
            {
                Id = 1,
                Status = MessageStatus.Read,
                Title = "Need IE7 Support?",
                CreatedDate = new DateTime(2018, 8, 10, 12, 11, 42),
                MemberRemindMessage = "IE8 has some issues with @font-face when combined with :before. Font Awesome uses that combination. If a page is cached, and loaded without the mouse over the window (i.e. hit the refresh button or load something in an iframe) then the page gets rendered before the font loads. Hovering over the page (body) will show some of the icons and hovering over the remaining icons will show those as well. See issue #954 for details.",
            });

            return Json(messages);
        }

        #endregion

        #region 修改密码:ChangePassword  &  修改邮箱/手机：ChangeMailPhone
        /// <summary>
        /// 主页修改密码
        /// </summary>
        /// <param name="model"></param>
        /// <returns></returns>
        [HttpPost]
        public async Task<JsonResult> ChangePassword(ChangePasswordViewModel model)
        {
            if (!User.Identity.IsAuthenticated)
                return Json(new { success = false, message = "用户未登陆，不能修改密码." });

            if (!ModelState.IsValid)
                return Json(new { success = false, message = "数据有误,请重新输入." });

            using (var scope = ServiceProvider.CreateScope())
            {
                var accountService = scope.ServiceProvider.GetRequiredService<IAccountApiService>();
                var result = await accountService.ChangePasswordAsync(CurrentUserId, model.OldPassword, model.NewPassword);

                return result.success
                    ? Json(new { success = true, message = "密码修改成功." })
                    : Json(
                        new
                        {
                            success = false,
                            message =
                                result.message.Contains("密码不正确。")
                                    ? "当前密码不正确."
                                    : string.Join("<br/>", result.message.ToList())
                        });
            }
        }
        /// <summary>
        /// 主页修改邮箱及手机号
        /// </summary>
        /// <param name="model"></param>
        /// <returns></returns>
        [HttpPost]
        public async Task<JsonResult> ChangeMailPhone(ChangeEmailPhoneViewModel model)
        {
            if (!User.Identity.IsAuthenticated)
                return Json(new { success = false, message = "用户未登陆，不能修改邮箱及手机号." });

            if (!ModelState.IsValid)
                return Json(new { success = false, message = "数据有误,请重新输入." });

            using (var scope = ServiceProvider.CreateScope())
            {
                var accountService = scope.ServiceProvider.GetRequiredService<IAccountApiService>();
                var result = await accountService.ChangeMailPhoneAsync(CurrentUserId, model.Email, model.Phone);

                return result.success
                    ? Json(new { success = true, message = "邮箱及手机号修改成功." })
                    : Json(new { success = false, message = "邮箱及手机号修改失败，请重试." });
            }
        }
        #endregion

        #region 退出：SignOut

        public async Task SignOut()
        {
            if (User.Identity.IsAuthenticated)
            {
                await HttpContext.SignOutAsync(MVC.Core.Constants.OpenIdConnectConstants.AuthScheme);
                await HttpContext.SignOutAsync(MVC.Core.Constants.OpenIdConnectConstants.ChallengeScheme);

                RemoveCurrentUserCache();
            }
        }

        #endregion

        #region 视图引擎
        /// <summary>
        /// 使用RazorViewEngine,生成视图页的字符串
        /// </summary>
        /// <param name="viewName">视图名称，例如：Home/Index</param>
        /// <param name="model">传入视图的ViewModel对象</param>
        /// <returns>视图字符串</returns>
        protected async Task<string> RenderToStringAsync(string viewName, object model)
        {
            try
            {
                var httpContext = new DefaultHttpContext { RequestServices = ServiceProvider };
                var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());

                var razorViewEngine = ServiceProvider.GetService<IRazorViewEngine>();
                var tempDataProvider = ServiceProvider.GetService<ITempDataProvider>();
                using (var sw = new StringWriter())
                {
                    var viewResult = razorViewEngine.FindView(actionContext, viewName, false);

                    if (viewResult.View == null)
                    {
                        throw new ArgumentNullException($"{viewName} does not match any available view");
                    }

                    var viewDictionary = new ViewDataDictionary(new EmptyModelMetadataProvider(), new ModelStateDictionary())
                    {
                        Model = model
                    };

                    var viewContext = new ViewContext(
                        actionContext,
                        viewResult.View,
                        viewDictionary,
                        new TempDataDictionary(actionContext.HttpContext, tempDataProvider),
                        sw,
                        new HtmlHelperOptions()
                    );

                    await viewResult.View.RenderAsync(viewContext);
                    return sw.ToString();
                }
            }
            catch (Exception ex)
            {
                Logger.LogError(string.Format("{0} RenderToStringAsync throw exception: {1} StackTrace: {2}", viewName, ex.Message, ex.StackTrace));
                return string.Empty;
            }
        }

        #endregion

        /// <summary>
        /// 获取特定编码页面的Form的相关参数
        /// </summary>
        /// <param name="name">表单Key</param>
        /// <param name="encoding">编码</param>
        /// <returns></returns>
        protected string GetFormValueByNameWithEncoding(string name, Encoding encoding)
        {
            var stream = this.Request.Body;
            var bytes = new byte[stream.Length];
            stream.Read(bytes, 0, bytes.Length);
            // 设置当前流的位置为流的开始
            stream.Seek(0, SeekOrigin.Begin);

            var formNameValue = FillFromEncodedBytes(bytes, encoding);
            return formNameValue[name];
        }
        private NameValueCollection FillFromEncodedBytes(byte[] bytes, Encoding encoding)
        {
            var formCollection = new NameValueCollection();
            int num = (bytes != null) ? bytes.Length : 0;
            for (int i = 0; i < num; i++)
            {
                string str;
                string str2;
                int offset = i;
                int num4 = -1;
                while (i < num)
                {
                    byte num5 = bytes[i];
                    if (num5 == 0x3d)
                    {
                        if (num4 < 0)
                        {
                            num4 = i;
                        }
                    }
                    else if (num5 == 0x26)
                    {
                        break;
                    }
                    i++;
                }
                if (num4 >= 0)
                {
                    str = HttpUtility.UrlDecode(bytes, offset, num4 - offset, encoding);
                    str2 = HttpUtility.UrlDecode(bytes, num4 + 1, (i - num4) - 1, encoding);
                }
                else
                {
                    str = null;
                    str2 = HttpUtility.UrlDecode(bytes, offset, i - offset, encoding);
                }
                formCollection.Add(str, str2);
                if ((i == (num - 1)) && (bytes[i] == 0x26))
                {
                    formCollection.Add(null, string.Empty);
                }
            }

            return formCollection;
        }

        /// <summary>
        /// 获取域名的顶级域名
        /// </summary>
        /// <param name="domain"></param>
        /// <returns></returns>
        protected string GetTopDomainName(string domain)
        {
            domain = domain.Trim().ToLower();
            //顶级域名太多了，暂时列出常用的
            string rootDomain = ".com.cn|.gov.cn|.cn|.com|.net|.org|.so|.co|.mobi|.tel|.biz|.info|.name|.me|.cc|.tv|.asiz|.hk";
            if (domain.StartsWith("http://")) domain = domain.Replace("http://", "");
            if (domain.StartsWith("https://")) domain = domain.Replace("https://", "");
            if (domain.StartsWith("www.")) domain = domain.Replace("www.", "");
            //safsd.asdfasdf.baidu.com.cn/ssssd/s/b/d/hhh.html?domain=sfsdf.com.cn&id=1
            if (domain.IndexOf("/") > 0)
                domain = domain.Substring(0, domain.IndexOf("/"));
            //safsd.asdfasdf.baidu.com.cn
            foreach (string item in rootDomain.Split('|'))
            {
                if (domain.EndsWith(item))
                {
                    domain = domain.Replace(item, "");
                    if (domain.LastIndexOf(".") > 0)
                    {
                        domain = domain.Replace(domain.Substring(0, domain.LastIndexOf(".") + 1), "");
                    }
                    return domain + item;
                }
            }
            return "";
        }

    }
}
